#ifndef LKMC_GIC_H
#define LKMC_GIC_H

#include <lkmc/aarch64.h>

#if LKMC_QEMU

/* info qtree contains:
 *
 *   dev: arm_gic, id ""
 *    mmio 0000000008000000/0000000000001000
 *    mmio 0000000008010000/0000000000002000 */
#define GIC_GICD_BASE 0x08000000
#define GIC_GICC_BASE 0x08010000
#define TIMER_IRQ     27

#elif LKMC_GEM5

/* On source at: https://github.com/gem5/gem5/blob/f525028c126af33da532f6703a152d81d900dcf7/src/dev/arm/RealView.py#L952
 *
 * On m5out/config.ini at:
 *
 * * system.realview.gic.dist_addr=738201600
 * * system.realview.gic.cpu_addr=738205696
 *
 * On auto-generated DTB in m5out/system.dtb after converstion to dts:
 *
 * interrupt-controller {
 *       compatible = "gem5,gic", "arm,cortex-a15-gic", "arm,cortex-a9-gic";
 *       reg = <0x0 0x2c001000 0x0 0x1000 0x0 0x2c002000 0x0 0x1000 0x0 0x2c004000 0x0 0x2000 0x0 0x2c006000 0x0 0x2000>; * */
#define GIC_GICD_BASE 0x2c001000
#define GIC_GICC_BASE 0x2c002000
#define TIMER_IRQ     30

#endif

#define GIC_INT_MAX    64
#define GIC_PRIO_MAX   16
#define GIC_INTNO_SGI0 0
#define GIC_INTNO_PPI0 16
#define GIC_INTNO_SPI0 32
#define GIC_PRI_SHIFT  4
#define GIC_PRI_MASK   0x0f

typedef int32_t irq_no;

#define GIC_GICD_INT_PER_REG            (32)
#define GIC_GICD_IPRIORITY_PER_REG      (4)
#define GIC_GICD_IPRIORITY_SIZE_PER_REG (8)
#define GIC_GICD_ITARGETSR_CORE0_TARGET_BMAP (0x01010101)
#define GIC_GICD_ITARGETSR_PER_REG      (4)
#define GIC_GICD_ITARGETSR_SIZE_PER_REG (8)
#define GIC_GICD_ICFGR_PER_REG          (16)
#define GIC_GICD_ICFGR_SIZE_PER_REG     (2)
#define GIC_GICD_ICENABLER_PER_REG      (32)
#define GIC_GICD_ISENABLER_PER_REG      (32)
#define GIC_GICD_ICPENDR_PER_REG        (32)
#define GIC_GICD_ISPENDR_PER_REG        (32)

/* 8.12 The GIC CPU interface register map */
#define GIC_GICC_CTLR     (GIC_GICC_BASE + 0x000) /* CPU Interface Control Register */
#define GIC_GICC_PMR      (GIC_GICC_BASE + 0x004) /* Interrupt Priority Mask Register */
#define GIC_GICC_BPR      (GIC_GICC_BASE + 0x008) /* Binary Point Register */
#define GIC_GICC_IAR      (GIC_GICC_BASE + 0x00C) /* Interrupt Acknowledge Register */
#define GIC_GICC_EOIR     (GIC_GICC_BASE + 0x010) /* End of Interrupt Register */
#define GIC_GICC_RPR      (GIC_GICC_BASE + 0x014) /* Running Priority Register */
#define GIC_GICC_HPIR     (GIC_GICC_BASE + 0x018) /* Highest Pending Interrupt Register */
#define GIC_GICC_ABPR     (GIC_GICC_BASE + 0x01C) /* Aliased Binary Point Register */
#define GIC_GICC_IIDR     (GIC_GICC_BASE + 0x0FC) /* CPU Interface Identification Register */

/* 8.13.7 GICC_CTLR, CPU Interface Control Register */
#define GICC_CTLR_ENABLE            (0x1)   /* Enable GICC */
#define GICC_CTLR_DISABLE           (0x0)   /* Disable GICC */

/* 8.13.14 GICC_PMR, CPU Interface Priority Mask Register */
#define GICC_PMR_PRIO_MIN           (0xff)  /* The lowest level mask */
#define GICC_PMR_PRIO_HIGH          (0x0)   /* The highest level mask */

/* 8.13.6 GICC_BPR, CPU Interface Binary Point Register */
/* In systems that support only one Security state, when GICC_CTLR.CBPR == 0,
this register determines only Group 0 interrupt preemption. */
#define GICC_BPR_NO_GROUP           (0x0)   /* handle all interrupts */

/* 8.13.11 GICC_IAR, CPU Interface Interrupt Acknowledge Register */
#define GICC_IAR_INTR_IDMASK        (0x3ff) /* 0-9 bits means Interrupt ID */
#define GICC_IAR_SPURIOUS_INTR      (0x3ff) /* 1023 means spurious interrupt */

/* 8.8 The GIC Distributor register map */
#define GIC_GICD_CTLR           (GIC_GICD_BASE + 0x000)                 /* Distributor Control Register */
#define GIC_GICD_TYPER          (GIC_GICD_BASE + 0x004)                 /* Interrupt Controller Type Register */
#define GIC_GICD_IIDR           (GIC_GICD_BASE + 0x008)                 /* Distributor Implementer Identification Register */
#define GIC_GICD_IGROUPR(n)     (GIC_GICD_BASE + 0x080 + ( (n) * 4 ) )  /* Interrupt Group Registers */
#define GIC_GICD_ISENABLER(n)   (GIC_GICD_BASE + 0x100 + ( (n) * 4 ) )  /* Interrupt Set-Enable Registers */
#define GIC_GICD_ICENABLER(n)   (GIC_GICD_BASE + 0x180 + ( (n) * 4 ) )  /* Interrupt Clear-Enable Registers */
#define GIC_GICD_ISPENDR(n)     (GIC_GICD_BASE + 0x200 + ( (n) * 4 ) )  /* Interrupt Set-Pending Registers */
#define GIC_GICD_ICPENDR(n)     (GIC_GICD_BASE + 0x280 + ( (n) * 4 ) )  /* Interrupt Clear-Pending Registers */
#define GIC_GICD_ISACTIVER(n)   (GIC_GICD_BASE + 0x300 + ( (n) * 4 ) )  /* Interrupt Set-Active Registers */
#define GIC_GICD_ICACTIVER(n)   (GIC_GICD_BASE + 0x380 + ( (n) * 4 ) )  /* Interrupt Clear-Active Registers */
#define GIC_GICD_IPRIORITYR(n)  (GIC_GICD_BASE + 0x400 + ( (n) * 4 ) )  /* Interrupt Priority Registers */
#define GIC_GICD_ITARGETSR(n)   (GIC_GICD_BASE + 0x800 + ( (n) * 4 ) )  /* Interrupt Processor Targets Registers */
#define GIC_GICD_ICFGR(n)       (GIC_GICD_BASE + 0xc00 + ( (n) * 4 ) )  /* Interrupt Configuration Registers */
#define GIC_GICD_NSCAR(n)       (GIC_GICD_BASE + 0xe00 + ( (n) * 4 ) )  /* Non-secure Access Control Registers */
#define GIC_GICD_SGIR           (GIC_GICD_BASE + 0xf00 )                /* Software Generated Interrupt Register */
#define GIC_GICD_CPENDSGIR(n)   (GIC_GICD_BASE + 0xf10 + ( (n) * 4 ) )  /* SGI Clear-Pending Registers */
#define GIC_GICD_SPENDSGIR(n)   (GIC_GICD_BASE + 0xf20 + ( (n) * 4 ) )  /* SGI Set-Pending Registers */

/* 8.9.4 GICD_CTLR, Distributor Control Register */
#define GIC_GICD_CTLR_ENABLE    (0x1)   /* Enable GICD */
#define GIC_GICD_CTLR_DISABLE   (0x0)   /* Disable GICD */

/* 8.9.7 GICD_ICFGR<n>, Interrupt Configuration Registers */
#define GIC_GICD_ICFGR_LEVEL    (0x0)   /* level-sensitive */
#define GIC_GICD_ICFGR_EDGE     (0x2)   /* edge-triggered */

/* Register access macros for GICC */
#define REG_GIC_GICC_CTLR          ((volatile uint32_t *)(uintptr_t)GIC_GICC_CTLR)
#define REG_GIC_GICC_PMR           ((volatile uint32_t *)(uintptr_t)GIC_GICC_PMR)
#define REG_GIC_GICC_BPR           ((volatile uint32_t *)(uintptr_t)GIC_GICC_BPR)
#define REG_GIC_GICC_IAR           ((volatile uint32_t *)(uintptr_t)GIC_GICC_IAR)
#define REG_GIC_GICC_EOIR          ((volatile uint32_t *)(uintptr_t)GIC_GICC_EOIR)
#define REG_GIC_GICC_RPR           ((volatile uint32_t *)(uintptr_t)GIC_GICC_RPR)
#define REG_GIC_GICC_HPIR          ((volatile uint32_t *)(uintptr_t)GIC_GICC_HPIR)
#define REG_GIC_GICC_ABPR          ((volatile uint32_t *)(uintptr_t)GIC_GICC_ABPR)
#define REG_GIC_GICC_IIDR          ((volatile uint32_t *)(uintptr_t)GIC_GICC_IIDR)

/* Register access macros for GICD */
#define REG_GIC_GICD_CTLR             ((volatile uint32_t *)(uintptr_t)GIC_GICD_CTLR)
#define REG_GIC_GICD_TYPE             ((volatile uint32_t *)(uintptr_t)GIC_GICD_TYPE)
#define REG_GIC_GICD_IIDR             ((volatile uint32_t *)(uintptr_t)GIC_GICD_IIDR)
#define REG_GIC_GICD_IGROUPR(n)       ((volatile uint32_t *)(uintptr_t)GIC_GICD_IGROUPR(n))
#define REG_GIC_GICD_ISENABLER(n)     ((volatile uint32_t *)(uintptr_t)GIC_GICD_ISENABLER(n))
#define REG_GIC_GICD_ICENABLER(n)     ((volatile uint32_t *)(uintptr_t)GIC_GICD_ICENABLER(n))
#define REG_GIC_GICD_ISPENDR(n)       ((volatile uint32_t *)(uintptr_t)GIC_GICD_ISPENDR(n))
#define REG_GIC_GICD_ICPENDR(n)       ((volatile uint32_t *)(uintptr_t)GIC_GICD_ICPENDR(n))
#define REG_GIC_GICD_ISACTIVER(n)     ((volatile uint32_t *)(uintptr_t)GIC_GICD_ISACTIVER(n))
#define REG_GIC_GICD_ICACTIVER(n)     ((volatile uint32_t *)(uintptr_t)GIC_GICD_ICACTIVER(n))
#define REG_GIC_GICD_IPRIORITYR(n)    ((volatile uint32_t *)(uintptr_t)GIC_GICD_IPRIORITYR(n))
#define REG_GIC_GICD_ITARGETSR(n)     ((volatile uint32_t *)(uintptr_t)GIC_GICD_ITARGETSR(n))
#define REG_GIC_GICD_ICFGR(n)         ((volatile uint32_t *)(uintptr_t)GIC_GICD_ICFGR(n))
#define REG_GIC_GICD_NSCAR(n)         ((volatile uint32_t *)(uintptr_t)GIC_GICD_NSCAR(n))
#define REG_GIC_GICD_SGIR             ((volatile uint32_t *)(uintptr_t)GIC_GICD_SGIR)
#define REG_GIC_GICD_CPENDSGIR(n)     ((volatile uint32_t *)(uintptr_t)GIC_GICD_CPENDSGIR(n))
#define REG_GIC_GICD_SPENDSGIR(n)     ((volatile uint32_t *)(uintptr_t)GIC_GICD_SPENDSGIR(n))

void gic_initialize(void);
void gic_eoi(irq_no irq);
int gic_find_pending_irq(
    LkmcVectorExceptionFrame *exc __attribute__((unused)),
    irq_no *irqp
);
void gicd_disable_int(irq_no irq);
void gicd_enable_int(irq_no irq);
void gicd_clear_pending(irq_no irq);

/* Initialize GIC Controller */
static void init_gicc(void) {
    uint32_t pending_irq;

    /* Disable CPU interface */
    *REG_GIC_GICC_CTLR = GICC_CTLR_DISABLE;

    /* Set the priority level as the lowest priority.
     * Note: Higher priority corresponds to a lower Priority field value in the GIC_PMR.
     * In addition to this, writing 255 to the GICC_PMR always sets it to the
     * largest supported priority field value.
     */
    *REG_GIC_GICC_PMR = GICC_PMR_PRIO_MIN;

    /* Handle all of interrupts in a single group */
    *REG_GIC_GICC_BPR = GICC_BPR_NO_GROUP;

    /* Clear all of the active interrupts */
    for (
        pending_irq = (*REG_GIC_GICC_IAR & GICC_IAR_INTR_IDMASK);
        pending_irq != GICC_IAR_SPURIOUS_INTR;
        pending_irq = (*REG_GIC_GICC_IAR & GICC_IAR_INTR_IDMASK)
    )
        *REG_GIC_GICC_EOIR = *REG_GIC_GICC_IAR;

    /* Enable CPU interface */
    *REG_GIC_GICC_CTLR = GICC_CTLR_ENABLE;
}

static void init_gicd(void) {
    int32_t i, regs_nr;

    /* Diable distributor */
    *REG_GIC_GICD_CTLR = GIC_GICD_CTLR_DISABLE;

    /* Disable all IRQs */
    regs_nr = (GIC_INT_MAX + GIC_GICD_INT_PER_REG - 1) / GIC_GICD_INT_PER_REG;
    for (i = 0; regs_nr > i; ++i)
        *REG_GIC_GICD_ICENABLER(i) = ~((uint32_t)(0));

    /* Clear all pending IRQs */
    regs_nr = (GIC_INT_MAX + GIC_GICD_INT_PER_REG - 1) / GIC_GICD_INT_PER_REG;
    for (i = 0; regs_nr > i; ++i)
        *REG_GIC_GICD_ICPENDR(i) = ~((uint32_t)(0));

    /* Set all of interrupt priorities as the lowest priority */
    regs_nr = ( GIC_INT_MAX + GIC_GICD_IPRIORITY_PER_REG - 1) /
        GIC_GICD_IPRIORITY_PER_REG ;
    for (i = 0; regs_nr > i; i++)
        *REG_GIC_GICD_IPRIORITYR(i) = ~((uint32_t)(0));

    /* Set target of all of shared peripherals to processor 0 */
    for (i = GIC_INTNO_SPI0 / GIC_GICD_ITARGETSR_PER_REG;
         ( (GIC_INT_MAX + (GIC_GICD_ITARGETSR_PER_REG - 1) ) /
         GIC_GICD_ITARGETSR_PER_REG ) > i; ++i)
        *REG_GIC_GICD_ITARGETSR(i) =
            (uint32_t)GIC_GICD_ITARGETSR_CORE0_TARGET_BMAP;

    /* Set trigger type for all peripheral interrupts level triggered */
    for (i = GIC_INTNO_PPI0 / GIC_GICD_ICFGR_PER_REG;
         (GIC_INT_MAX + (GIC_GICD_ICFGR_PER_REG - 1)) / GIC_GICD_ICFGR_PER_REG > i; ++i)
        *REG_GIC_GICD_ICFGR(i) = GIC_GICD_ICFGR_LEVEL;

    /* Enable distributor */
    *REG_GIC_GICD_CTLR = GIC_GICD_CTLR_ENABLE;
}

/* Disable IRQ
 * @param[in] irq IRQ number
 */
void gicd_disable_int(irq_no irq) {
    *REG_GIC_GICD_ICENABLER( (irq / GIC_GICD_ICENABLER_PER_REG) ) =
        1U << ( irq % GIC_GICD_ICENABLER_PER_REG );
}

/* Enable IRQ
 * @param[in] irq IRQ number
 */
void gicd_enable_int(irq_no irq) {
    *REG_GIC_GICD_ISENABLER( (irq / GIC_GICD_ISENABLER_PER_REG) ) =
        1U << ( irq % GIC_GICD_ISENABLER_PER_REG );
}

/* Clear a pending interrupt
 * @param[in] irq IRQ number
 */
void gicd_clear_pending(irq_no irq) {
    *REG_GIC_GICD_ICPENDR( (irq / GIC_GICD_ICPENDR_PER_REG) ) =
        1U << ( irq % GIC_GICD_ICPENDR_PER_REG );
}


/* Probe pending interrupt
 * @param[in] irq IRQ number
 */
static int gicd_probe_pending(irq_no irq) {
    int is_pending;

    is_pending = ( *REG_GIC_GICD_ISPENDR( (irq / GIC_GICD_ISPENDR_PER_REG) ) &
        ( 1U << ( irq % GIC_GICD_ISPENDR_PER_REG ) ) );
    return ( is_pending != 0 );
}

/* Set an interrupt target processor
 * @param[in] irq IRQ number
 * @param[in] p   Target processor mask
 * 0x1 processor 0
 * 0x2 processor 1
 * 0x4 processor 2
 * 0x8 processor 3
 */
static void gicd_set_target(irq_no irq, uint32_t p){
    uint32_t  shift;
    uint32_t    reg;

    shift = (irq % GIC_GICD_ITARGETSR_PER_REG) * GIC_GICD_ITARGETSR_SIZE_PER_REG;
    reg = *REG_GIC_GICD_ITARGETSR(irq / GIC_GICD_ITARGETSR_PER_REG);
    reg &= ~( ((uint32_t)(0xff)) << shift);
    reg |= (p << shift);
    *REG_GIC_GICD_ITARGETSR(irq / GIC_GICD_ITARGETSR_PER_REG) = reg;
}

/* Set an interrupt priority
 * @param[in] irq  IRQ number
 * @param[in] prio Interrupt priority in Arm specific expression
 */
static void gicd_set_priority(irq_no irq, uint32_t prio){
    uint32_t  shift;
    uint32_t    reg;

    shift = (irq % GIC_GICD_IPRIORITY_PER_REG) * GIC_GICD_IPRIORITY_SIZE_PER_REG;
    reg = *REG_GIC_GICD_IPRIORITYR(irq / GIC_GICD_IPRIORITY_PER_REG);
    reg &= ~(((uint32_t)(0xff)) << shift);
    reg |= (prio << shift);
    *REG_GIC_GICD_IPRIORITYR(irq / GIC_GICD_IPRIORITY_PER_REG) = reg;
}

/* Configure IRQ
 * @param[in] irq     IRQ number
 * @param[in] config  Configuration value for GICD_ICFGR
 */
static void gicd_config(irq_no irq, unsigned int config) {
    uint32_t shift;
    uint32_t reg;

    shift = (irq % GIC_GICD_ICFGR_PER_REG) * GIC_GICD_ICFGR_SIZE_PER_REG; /* GICD_ICFGR has 16 fields, each field has 2bits. */
    reg = *REG_GIC_GICD_ICFGR( irq / GIC_GICD_ICFGR_PER_REG);
    reg &= ~( ( (uint32_t)(0x03) ) << shift );  /* Clear the field */
    reg |= ( ( (uint32_t)config ) << shift );  /* Set the value to the field correponding to irq */
    *REG_GIC_GICD_ICFGR( irq / GIC_GICD_ICFGR_PER_REG) = reg;
}

/* Send End of Interrupt to IRQ line for GIC
 * @param[in] ctrlr   IRQ controller information
 * @param[in] irq     IRQ number
 */
void gic_eoi(irq_no irq) {
    gicd_clear_pending(irq);
}

/* Initialize GIC IRQ controller */
/* RyanYao: 2018/07/20
 * I supppose the current access is security, because GICD_CTLR.DS is 0b0 and
 * we can access.
 */
void gic_initialize(void) {
    init_gicd();
    init_gicc();
    gicd_config(TIMER_IRQ, GIC_GICD_ICFGR_EDGE);
    gicd_set_priority(TIMER_IRQ, 0 << GIC_PRI_SHIFT);
    gicd_set_target(TIMER_IRQ, 0x1); /* processor 0 */
    gicd_clear_pending(TIMER_IRQ);
    gicd_enable_int(TIMER_IRQ);
}


/* Find pending IRQ
 * @param[in]     exc  An exception frame
 * @param[in,out] irqp An IRQ number to be processed
 * @return        0 if found, 1 if not found
 */
int gic_find_pending_irq(
    LkmcVectorExceptionFrame *exc __attribute__((unused)),
    irq_no *irqp
) {
    int rc;
    irq_no i;
    for (i = 0; GIC_INT_MAX > i; ++i) {
        if (gicd_probe_pending(i)) {
            rc = 0;
            *irqp = i;
            goto found;
        }
    }
    rc = 1;
found:
    return rc;
}

#endif
